package com.shuaqiu.simback.dao.impl;

import com.shuaqiu.simback.model.Model;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.util.StringUtils;

import javax.inject.Named;
import java.beans.PropertyDescriptor;
import java.util.ArrayList;
import java.util.List;

/**
 * @author shuaqiu on 2013-09-16.
 */
@Named
public class SqlBuilder {

    /**
     * @Author qiushaohua @ Sep 16, 2013
     */
    public <T extends Model> String buildSelect(Class<T> clazz) {
        String[] fieldNames = getFieldNames((Class<?>) clazz);
        if (fieldNames == null || fieldNames.length == 0) {
            throw new IllegalArgumentException("It's not a Plain Object: " + clazz.getName());
        }

        StringBuilder sql = new StringBuilder("select ");
        sql.append(StringUtils.arrayToCommaDelimitedString(fieldNames));
        sql.append(" from ");
        sql.append(Str.underscoreName(clazz.getSimpleName()));
        return sql.toString();
    }

    public <T extends Model> String buildDelete(Class<T> clazz) {
        return "delete from " + Str.underscoreName(clazz.getSimpleName());
    }

    public <T extends Model> String buildExist(T bo) {
        return "select count(id) from " + Str.underscoreName(bo.getClass().getSimpleName()) + " where id = ?";
    }

    public <T extends Model> String buildInsert(T bo) {
        Class<? extends Model> clazz = bo.getClass();

        String[] fieldNames = getFieldNamesIgnoreId(clazz);
        if (fieldNames == null || fieldNames.length == 0) {
            throw new IllegalArgumentException("It's not a Plain Object: " + clazz.getName());
        }

        StringBuilder sql = new StringBuilder("insert into ");
        sql.append(Str.underscoreName(clazz.getSimpleName()));
        sql.append(" ( ");
        sql.append(StringUtils.arrayToCommaDelimitedString(fieldNames));
        sql.append(") values (");
        for (int i = 0; i < fieldNames.length; i++) {
            if (i > 0) {
                sql.append(", ");
            }
            sql.append("?");
        }
        sql.append(") ");
        return sql.toString();
    }

    public <T extends Model> Object[] buildInsertParams(T bo) {
        List<Object> args = buildParam(bo);
        return args.toArray(new Object[args.size()]);
    }

    public <T extends Model> String buildUpdate(T bo) {
        Class<? extends Model> clazz = bo.getClass();

        String[] fieldNames = getFieldNamesIgnoreId(clazz);
        if (fieldNames == null || fieldNames.length == 0) {
            throw new IllegalArgumentException("It's not a Plain Object: " + clazz.getName());
        }

        StringBuilder sql = new StringBuilder("update ");
        sql.append(Str.underscoreName(clazz.getSimpleName()));
        sql.append(" set ");
        int index = 0;
        for (String field : fieldNames) {
            if (index > 0) {
                sql.append(", ");
            }
            sql.append(Str.underscoreName(field));
            sql.append(" = ?");
            index++;
        }
        sql.append(" where id = ? ");
        return sql.toString();
    }

    public <T extends Model> Object[] buildUpdateParams(T bo) {
        List<Object> args = buildParam(bo);
        args.add(bo.getId());

        return args.toArray(new Object[args.size()]);
    }

    private <T extends Model> List<Object> buildParam(T bo) {
        Class<? extends Model> clazz = bo.getClass();

        PropertyDescriptor[] properties = getProperties(clazz);
        if (properties == null || properties.length == 0) {
            throw new IllegalArgumentException("It's not a Plain Object: " + clazz.getName());
        }

        String[] ignored = new String[]{"id", "class"};
        List<Object> args = new ArrayList<Object>(properties.length);
        for (PropertyDescriptor property : properties) {
            if (isIgnore(property, ignored)) {
                continue;
            }
            Object arg = getPropertyValue(bo, property);
            args.add(arg);
        }
        return args;
    }

    private String[] getFieldNames(Class<?> clazz) {
        return getFieldNames(clazz, "class");
    }

    private String[] getFieldNamesIgnoreId(Class<?> clazz) {
        return getFieldNames(clazz, "id", "class");
    }

    private String[] getFieldNames(Class<?> clazz, String... ignores) {
        PropertyDescriptor[] properties = getProperties(clazz);

        List<String> names = new ArrayList<String>(properties.length);
        for (PropertyDescriptor prop : properties) {
            if (isIgnore(prop, ignores)) {
                continue;
            }
            names.add(Str.underscoreName(prop.getName()));
        }
        return names.toArray(new String[names.size()]);
    }

    private boolean isIgnore(PropertyDescriptor prop, String[] ignores) {
        for (String key : ignores) {
            if (key.equals(prop.getName())) {
                return true;
            }
        }
        return false;
    }

    private PropertyDescriptor[] getProperties(Class<?> clazz) {
        PropertyDescriptor[] descriptors = BeanUtils.getPropertyDescriptors(clazz);
        return descriptors;
    }

    private <T extends Model> Object getPropertyValue(T bo, PropertyDescriptor property) {
        BeanWrapper wrapper = new BeanWrapperImpl(bo);
        return wrapper.getPropertyValue(property.getName());
    }
}
